﻿using System.Drawing;
using System.Drawing.Imaging;

namespace CSLib.Imaging
{
	/// <summary>
	/// Bitmap画像を高速に扱うクラス。
	/// </summary>
	public class BitmapEx
	{
		/// <summary>
		/// Bitmap image.
		/// </summary>
		private Bitmap _bmp;

		/// <summary>
		/// Bitmap data.
		/// </summary>
		private BitmapData _bmpData;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="bmp"></param>
		public BitmapEx(Bitmap bmp)
		{
			_bmp = bmp;
		}

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="filename"></param>
		public BitmapEx(string filename)
		{
			_bmp = (Bitmap)Image.FromFile(filename);
		}

		/// <summary>
		/// Bitmap画像の取得。
		/// </summary>
		public Bitmap Bmp { get { return _bmp; } }

		/// <summary>
		/// Bitmap画像を3次元配列で取得。
		/// </summary>
		/// <returns></returns>
		public byte[, ,] GetMat3D()
		{
			this.lockBits();
			byte[, ,] mat = new byte[_bmpData.Width, _bmpData.Height, 3];

			unsafe
			{
				byte* ptr = (byte*)_bmpData.Scan0;
				for (int y = 0; y < _bmpData.Height; y++)
				{
					for (int x = 0; x < _bmpData.Width; x++)
					{
						int pos = x * 3 + _bmpData.Stride * y;
						mat[x, y, 0] = ptr[pos + 0]; // B
						mat[x, y, 1] = ptr[pos + 1]; // G
						mat[x, y, 2] = ptr[pos + 2]; // R
					}
				}
			}
			this.unlockBits();

			return mat;
		}

		/// <summary>
		/// Bitmap画像を2次元配列の値（0xRRGGBB）で取得。
		/// </summary>
		/// <returns></returns>
		public int[,] GetMat2D()
		{
			this.lockBits();
			int[,] mat = new int[_bmpData.Width, _bmpData.Height];

			unsafe
			{
				byte* ptr = (byte*)_bmpData.Scan0;
				for (int y = 0; y < _bmpData.Height; y++)
				{
					for (int x = 0; x < _bmpData.Width; x++)
					{
						int pos = x * 3 + _bmpData.Stride * y;
						mat[x, y] = ptr[pos + 2] << 16 | ptr[pos + 1] << 8 | ptr[pos]; // 0xRRGGBB
					}
				}
			}
			this.unlockBits();

			return mat;
		}

		/// <summary>
		/// Bitmap画像の各ピクセル値（0xRRGGBB）を配列で返す。
		/// </summary>
		/// <returns></returns>
		public int[] GetValues()
		{
			this.lockBits();
			int[] values = new int[_bmpData.Width * _bmpData.Height];

			unsafe
			{
				int i = 0;
				byte* ptr = (byte*)_bmpData.Scan0;
				for (int y = 0; y < _bmpData.Height; y++)
				{
					for (int x = 0; x < _bmpData.Width; x++)
					{
						int pos = x * 3 + _bmpData.Stride * y;
						values[i++] = ptr[pos + 2] << 16 | ptr[pos + 1] << 8 | ptr[pos]; // 0xRRGGBB
					}
				}
			}
			this.unlockBits();

			return values;
		}


		/// <summary>
		/// 3次元配列のデータをBitmap画像に設定。
		/// </summary>
		/// <param name="mat"></param>
		public void SetMat3D(byte[, ,] mat)
		{
			this.lockBits();
			unsafe
			{
				byte* ptr = (byte*)_bmpData.Scan0;
				for (int y = 0; y < _bmpData.Height; y++)
				{
					for (int x = 0; x < _bmpData.Width; x++)
					{
						int pos = x * 3 + _bmpData.Stride * y;
						ptr[pos + 0] = (byte)mat[x, y, 0]; // B
						ptr[pos + 1] = (byte)mat[x, y, 1]; // G
						ptr[pos + 2] = (byte)mat[x, y, 2]; // R
					}
				}
			}
			this.unlockBits();
		}

		/// <summary>
		/// 2次元配列のデータ（0xRRGGBB形式）をBitmap画像に設定。
		/// </summary>
		/// <param name="mat"></param>
		public void SetMat2D(int[,] mat)
		{
			this.lockBits();
			unsafe
			{
				byte* ptr = (byte*)_bmpData.Scan0;
				for (int y = 0; y < _bmpData.Height; y++)
				{
					for (int x = 0; x < _bmpData.Width; x++)
					{
						int pos = x * 3 + _bmpData.Stride * y;
						ptr[pos + 0] = (byte)(mat[x, y] & 0x0000FF); // B
						ptr[pos + 1] = (byte)((mat[x, y] & 0x00FF00) >> 8); // G
						ptr[pos + 2] = (byte)((mat[x, y] & 0xFF0000) >> 16); // R
					}
				}
			}
			this.unlockBits();
		}

		/// <summary>
		/// Lock bits.
		/// </summary>
		private void lockBits()
		{
			_bmpData = _bmp.LockBits(new Rectangle(Point.Empty, _bmp.Size), ImageLockMode.ReadWrite, _bmp.PixelFormat);
		}

		/// <summary>
		/// Unlock bits.
		/// </summary>
		private void unlockBits()
		{
			_bmp.UnlockBits(_bmpData);
		}

	}
}
